package com.yzcrm.common.lucene;

import cn.hutool.core.util.ObjectUtil;
import com.yzcrm.common.constant.BaseConstant;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.highlight.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.wltea.analyzer.lucene.IKAnalyzer;

import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class LuceneIndex {

    // 定义分词器
    private static Analyzer analyzer = new IKAnalyzer();

    private static Directory directoryFile = null;
    private static Directory directoryResource = null;
    private static Directory directoryCourse = null;
    private static Directory directoryBbs = null;

    private static IndexReader ireaderFile = null;
    private static IndexReader ireaderResource = null;
    private static IndexReader ireaderCourse = null;
    private static IndexReader ireaderBbs = null;
    private static MultiReader multiReader = null;


    static {

        try {

            // 初始化索引目录（软件初次部署时使用）
            // 文件
            IndexWriterConfig writerConfig = new IndexWriterConfig(analyzer);
            writerConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
            Directory directoryFile = FSDirectory.open(Paths.get(BaseConstant.LUCENE_PATH_FILE));
            IndexWriter indexWriter = new IndexWriter(directoryFile, writerConfig);
            IndexReader ireaderFile = DirectoryReader.open(indexWriter);
            try {
                indexWriter.close();
                ireaderFile.close();
                directoryFile.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            // 资源
            writerConfig = new IndexWriterConfig(analyzer);
            writerConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
            Directory directoryResource = FSDirectory.open(Paths.get(BaseConstant.LUCENE_PATH_RESOURCE));
            indexWriter = new IndexWriter(directoryResource, writerConfig);
            IndexReader ireaderResource = DirectoryReader.open(indexWriter);
            try {
                indexWriter.close();
                ireaderResource.close();
                directoryResource.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            // 课程
            writerConfig = new IndexWriterConfig(analyzer);
            writerConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
            Directory directoryCourse = FSDirectory.open(Paths.get(BaseConstant.LUCENE_PATH_COURSE));
            indexWriter = new IndexWriter(directoryCourse, writerConfig);
            IndexReader ireaderCourse = DirectoryReader.open(indexWriter);
            try {
                indexWriter.close();
                ireaderCourse.close();
                directoryCourse.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            // BBS
            writerConfig = new IndexWriterConfig(analyzer);
            writerConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
            Directory directoryBbs = FSDirectory.open(Paths.get(BaseConstant.LUCENE_PATH_BBS));
            indexWriter = new IndexWriter(directoryBbs, writerConfig);
            IndexReader ireaderBbs = DirectoryReader.open(indexWriter);
            try {
                indexWriter.close();
                ireaderBbs.close();
                directoryBbs.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 增加索引
     *
     * @param id
     * @param title
     * @param content
     */
    public void addContent(String path, String label, String id, String title, String content) {

        Document doc = this.createDoc(label, id, title, content);

        this.write(doc, path);
    }

    /**
     * 用于将数据库中的数据解析为一个个关键字词存储到索引文件中
     *
     * @param doc
     */
    private void write(Document doc, String path) {

        IndexWriter iwriter = null;

        try {

            //索引库的存储目录
            Directory directory = FSDirectory.open(Paths.get(path));
            //关联当前lucence版本和分值器
            IndexWriterConfig config = new IndexWriterConfig(analyzer);
            config.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
            //传入目录和分词器
            iwriter = new IndexWriter(directory, config);
            //写入到目录文件中
            iwriter.addDocument(doc);
            //提交事务
            iwriter.commit();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            //关闭流
            if (iwriter != null) {
                try {
                    iwriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private Document createDoc(String label, String id, String title, String content) {

        // 索引并存储
        FieldType idType = new FieldType();
        idType.setIndexOptions(IndexOptions.DOCS);
        idType.setStored(true);

        // 设置标题索引文档、词项频率、位移信息和偏移量，存储并词条化
        FieldType titleType = new FieldType();
        titleType.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS);
        titleType.setStored(true);
        titleType.setTokenized(true);

        // 设置内容索引文档、词项频率、位移信息和偏移量，存储并词条化
        FieldType contentType = new FieldType();
        contentType.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS);
        contentType.setStored(true);
        contentType.setTokenized(true);
        contentType.setStoreTermVectors(true);
        contentType.setStoreTermVectorPositions(true);
        contentType.setStoreTermVectorOffsets(true);

        FieldType labelType = new FieldType();
        labelType.setStored(true);

        // 创建Document对象
        Document doc = new Document();
        // 添加到Document中
        doc.add(new Field("id", id, idType));
        doc.add(new Field("title", title, titleType));
        doc.add(new Field("content", content, contentType));
        doc.add(new Field("label", label, labelType));

        return doc;
    }

    /**
     * 从索引库中检索
     *
     * @param searchContent
     * @return
     * @throws Exception
     */
    public List<Map> search(String searchContent) throws Exception {

//        //索引库的存储目录
//        Directory directory = FSDirectory.open(Paths.get(BaseConstant.LUCENE_PATH_FILE));
//        //读取索引库的存储目录
//        DirectoryReader ireader = DirectoryReader.open(directory);
        //搜索类
        IndexSearcher isearcher = this.getSearcher();//new IndexSearcher(multiReader);


//        //lucence查询解析器，用于指定查询的属性名和分词器
//        QueryParser parser = new QueryParser(field, analyzer);
//        //搜索
//        Query query = parser.parse(value);

        QueryParser queryParser = new QueryParser("*", analyzer);
        Query query = queryParser.parse("title:'" + searchContent + "' or content:'" + searchContent + "'");


        //最终被分词后添加的前缀和后缀处理器，默认是粗体<B></B>
        SimpleHTMLFormatter htmlFormatter = new SimpleHTMLFormatter("<font color=red>", "</font>");
        //高亮搜索的词添加到高亮处理器中
        Highlighter highlighter = new Highlighter(htmlFormatter, new QueryScorer(query));

        //获取搜索的结果，指定返回document返回的个数
        ScoreDoc[] hits = isearcher.search(query, 100).scoreDocs;
        List<Map> list = new ArrayList<Map>();
        for (int i = 0; i < hits.length; i++) {
            int id = hits[i].doc;
            Document hitDoc = isearcher.doc(hits[i].doc);
            Map map = new HashMap();
            map.put("id", hitDoc.get("id"));

            //获取到title
            String title = hitDoc.get("title");
            //将查询的词和搜索词匹配，匹配到添加前缀和后缀
            TokenStream tokenStream = TokenSources.getAnyTokenStream(isearcher.getIndexReader(), id, "title", analyzer);
            //传入的第二个参数是查询的值
            TextFragment[] frag = highlighter.getBestTextFragments(tokenStream, title, false, 10);
            String titleValue = "";
            for (int j = 0; j < frag.length; j++) {
                if ((frag[j] != null) && (frag[j].getScore() > 0)) {
                    //获取 title 的值
                    titleValue = ((frag[j].toString()));
                }
            }
            titleValue = ObjectUtil.isEmpty(titleValue) ? hitDoc.get("title") : titleValue;
            map.put("title", titleValue);

            //获取到content
            String content = hitDoc.get("content");
            //将查询的词和搜索词匹配，匹配到添加前缀和后缀
            tokenStream = TokenSources.getAnyTokenStream(isearcher.getIndexReader(), id, "content", analyzer);
            //传入的第二个参数是查询的值
            frag = highlighter.getBestTextFragments(tokenStream, content, false, 10);
            String contentValue = "";
            for (int j = 0; j < frag.length; j++) {
                if ((frag[j] != null) && (frag[j].getScore() > 0)) {
                    //获取 content 的值
                    contentValue = ((frag[j].toString()));
                }
            }
            contentValue = ObjectUtil.isEmpty(contentValue) ? hitDoc.get("content") : contentValue;
            map.put("content", contentValue);

            map.put("label", hitDoc.get("label"));

            list.add(map);
        }
        return list;
    }

    /**
     * 根据查询条件删除lucene索引及文档数据
     */
    public void deleteContent(String path, String id) {

        //传入目录和分词器
        IndexWriter iwriter = null;

        try {

            //索引库的存储目录
            Directory directory = FSDirectory.open(Paths.get(path));
            //关联当前lucence版本和分值器
            IndexWriterConfig config = new IndexWriterConfig(analyzer);

            iwriter = new IndexWriter(directory, config);

            // 删除索引目录 id = 1的数据
            iwriter.deleteDocuments(new Term("id", id));
            iwriter.commit();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            if (ObjectUtil.isNotNull(iwriter)) {
                //关闭流
                try {
                    iwriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 更新索引
     */
    public void updateContent(String path, String label, String id, String title, String content) {

        //传入目录和分词器
        IndexWriter iwriter = null;

        try {

            //索引库的存储目录
            Directory directory = FSDirectory.open(Paths.get(path));
            //关联当前lucence版本和分值器
            IndexWriterConfig config = new IndexWriterConfig(analyzer);

            iwriter = new IndexWriter(directory, config);

            //创建document对象
            Document doc = this.createDoc(label, id, title, content);//new Document();
            //添加索引数据到doc对象
//            doc.add(new TextField("id", id, Field.Store.YES));
            //将新添加的doc数据替换索引中已经有的id为2的数据
            //执行步骤如下：查询数据--》查询到或未查询到--》替换/添加doc数据
            iwriter.updateDocument(new Term("id", id), doc);
            iwriter.commit();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            if (ObjectUtil.isNotNull(iwriter)) {
                //关闭流
                try {
                    iwriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }


    /**
     * 获取IndexReader
     */
    public IndexSearcher getSearcher() {
        try {
            if (ireaderCourse == null) {

                directoryCourse = FSDirectory.open(Paths.get(BaseConstant.LUCENE_PATH_COURSE));
//                IndexWriterConfig writerConfig = new IndexWriterConfig(analyzer);
//                writerConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
//                IndexWriter writer = new IndexWriter(directoryCourse, writerConfig);
                ireaderCourse = DirectoryReader.open(directoryCourse);
            } else {
                // 如果 IndexReader 不为空，就使用 DirectoryReader 打开一个索引变更过的 IndexReader 类
                IndexReader tr = DirectoryReader.openIfChanged((DirectoryReader) ireaderCourse);
                if (tr != null) {
                    ireaderCourse.close();
                    ireaderCourse = tr;
                }
            }

            if (ireaderResource == null) {

                directoryResource = FSDirectory.open(Paths.get(BaseConstant.LUCENE_PATH_RESOURCE));
//                IndexWriterConfig writerConfig = new IndexWriterConfig(analyzer);
//                writerConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
//                IndexWriter writer = new IndexWriter(directoryResource, writerConfig);
                ireaderResource = DirectoryReader.open(directoryResource);
            } else {
                // 如果 IndexReader 不为空，就使用 DirectoryReader 打开一个索引变更过的 IndexReader 类
                IndexReader tr = DirectoryReader.openIfChanged((DirectoryReader) ireaderResource);
                if (tr != null) {
                    ireaderResource.close();
                    ireaderResource = tr;
                }
            }

            if (ireaderFile == null) {
                directoryFile = FSDirectory.open(Paths.get(BaseConstant.LUCENE_PATH_FILE));
//                IndexWriterConfig writerConfig = new IndexWriterConfig(analyzer);
//                writerConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
//                IndexWriter writer = new IndexWriter(directoryFile, writerConfig);
                ireaderFile = DirectoryReader.open(directoryFile);
            } else {
                // 如果 IndexReader 不为空，就使用 DirectoryReader 打开一个索引变更过的 IndexReader 类
                IndexReader tr = DirectoryReader.openIfChanged((DirectoryReader) ireaderFile);
                if (tr != null) {
                    ireaderFile.close();
                    ireaderFile = tr;
                }
            }

            if (ireaderBbs == null) {
                directoryBbs = FSDirectory.open(Paths.get(BaseConstant.LUCENE_PATH_BBS));
//                IndexWriterConfig writerConfig = new IndexWriterConfig(analyzer);
//                writerConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
//                IndexWriter writer = new IndexWriter(directoryBbs, writerConfig);
                ireaderBbs = DirectoryReader.open(directoryBbs);
            } else {
                // 如果 IndexReader 不为空，就使用 DirectoryReader 打开一个索引变更过的 IndexReader 类
                IndexReader tr = DirectoryReader.openIfChanged((DirectoryReader) ireaderBbs);
                if (tr != null) {
                    ireaderBbs.close();
                    ireaderBbs = tr;
                }
            }


            multiReader = new MultiReader(ireaderCourse, ireaderFile, ireaderResource, ireaderBbs);

            return new IndexSearcher(multiReader);

        } catch (CorruptIndexException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

}
